home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / thread.c < prev    next >
Text File  |  1997-01-09  |  24KB  |  879 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     thread.c
  4.  
  5.     This module handles sorting subject windows into threads.
  6.     
  7.     Copyright © 1994-1997, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <ctype.h>
  13. #include <stdio.h>
  14.  
  15. #include "glob.h"
  16. #include "thread.h"
  17. #include "qsort.h"
  18. #include "newswatcher.h"
  19. #include "strutil.h"
  20. #include "memutil.h"
  21. #include "dialog.h"
  22. #include "header.h"
  23. #include "cache.h"
  24.  
  25.  
  26.  
  27. typedef struct TSortInfo {
  28.     char *canon;                /* ptr to canonical subject string */
  29.     TSubject *subject;            /* ptr to subject array element */
  30.     long index;                    /* index in subject array */
  31.     long number;                /* article number */
  32.     long partNum;                /* part number, or kMaxLong if not a part */
  33.     long numParts;                /* number of parts, or kMaxLong if not a part */
  34.     long threadHeadNumber;        /* article number of first article in thread  */
  35.     long threadOrdinal;            /* article ordinal in thread (1,2,3,...) */
  36.     Boolean fromCache;            /* true if from cache */
  37.     Boolean potentialPart;        /* true if potential part */
  38. } TSortInfo, *TSortInfoPtr, **TSortInfoHandle;
  39.  
  40.  
  41.  
  42. static CStr255 gGroupName;                /* group name */
  43. static Handle gStrings;                    /* handle to subject and author strings */
  44. static Boolean gParentIsUserGroupList;    /* true if parent window is user group list */
  45.  
  46.  
  47.  
  48. /*----------------------------------------------------------------------------
  49.     IsPartTail 
  50.     
  51.     Check string for tail of part indicator.
  52.     
  53.     Entry:    x = pointer to string.
  54.             lastChar = required trailing character, or 0 if none.
  55.             
  56.     Exit:    function result = true if end of part indicator, in which case:
  57.                 *end = pointer to character following end of part indicator.
  58.                 *partNum = part number.
  59.                 *numParts = number of parts.
  60. ----------------------------------------------------------------------------*/
  61.  
  62. static Boolean IsPartTail (char *x, char lastChar, char **end, long *partNum,
  63.     long *numParts)
  64. {
  65.     long pNum, numP;
  66.  
  67.     while (isLWSP(*x)) x++;
  68.     if (!isdigit(*x)) return false;
  69.     pNum = CrackNum(&x);
  70.     while (isLWSP(*x)) x++;
  71.     if (*x == '/' || *x == '|' || *x == '\\') {
  72.         x++;
  73.     } else if (strncmp(x, "of", 2) == 0) {
  74.         x += 2;
  75.     } else {
  76.         return false;
  77.     }
  78.     while (isLWSP(*x)) x++;
  79.     if (!isdigit(*x)) return false;
  80.     numP = CrackNum(&x);
  81.     if (pNum > numP) return false;
  82.     if (lastChar != 0) {
  83.         while (isLWSP(*x)) x++;
  84.         if (*x != lastChar) return false;
  85.         x++;
  86.     }
  87.     while (isLWSP(*x)) x++;
  88.     *end = x;
  89.     *partNum = pNum;
  90.     *numParts = numP;
  91.     return true;
  92. }
  93.  
  94.  
  95.  
  96. /*----------------------------------------------------------------------------
  97.     CheckForPartIndicator 
  98.     
  99.     Check subject for part indicator.
  100.     
  101.     Entry:    p = pointer to sort info record.
  102.             
  103.     Exit:    If the subject contains a part indicator:
  104.                 p->partNum = part number.
  105.                 p->numParts = number of parts.
  106.                 p->potentialPart = true if potential part.
  107.                 part indicator substring stripped from p->canon.
  108.                 if the part indicator is preceded by non-white space, the 
  109.                     tail of the subject is also stripped from p->canon.
  110.                 number of parts appended to end of p->canon.
  111.                 
  112.     Part indicators are defined as follows, using the notation of RFC 822:
  113.  
  114.         part-indicator = "part" part-n-of-m
  115.                        / "(" part-n-of-m ")"
  116.                        / "[" part-n-of-m "]"
  117.                        / "{" part-n-of-m "}"
  118.                        / "<" part-n-of-m ">"
  119.     
  120.         part-n-of-m = number "of" number
  121.                     / number "/" number
  122.                     / number "|" number        
  123.                     / number "\" number        ; first number <= second number
  124.  
  125.         number = 1*DIGIT
  126.         
  127.     If the subject contains a part indicator, any subject prefix in the 
  128.     following format is also stripped (for the comp.binaries.ibm.* groups):
  129.  
  130.         "v" number "i" number ":"
  131.         
  132.     A "potential" part is a "part-n-of-m", e.g., a part indicator without the
  133.     word "part" in front or the brackets. A "potential" part is special cased.
  134.     It is considered to be a part if and only if some other matching part is
  135.     also present in the subject list.
  136. ----------------------------------------------------------------------------*/
  137.  
  138. static void CheckForPartIndicator (TSortInfoPtr p)
  139. {
  140.     char *x, *end, *y;
  141.     long partNum, numParts;
  142.     Boolean potentialPart = false;
  143.     
  144.     x = p->canon;
  145.     while (true) {
  146.         x = strpbrk(x, "([{<p0123456789");
  147.         if (x == nil) return;
  148.         if (*x == '(') {
  149.             if (IsPartTail(x+1, ')', &end, &partNum, &numParts)) break;
  150.         } else if (*x == '[') {
  151.             if (IsPartTail(x+1, ']', &end, &partNum, &numParts)) break;
  152.         } else if (*x == '{') {
  153.             if (IsPartTail(x+1, '}', &end, &partNum, &numParts)) break;
  154.         } else if (*x == '<') {
  155.             if (IsPartTail(x+1, '>', &end, &partNum, &numParts)) break;
  156.         } else if (*x == 'p' && strncmp(x, "part", 4) == 0)  {
  157.             if (IsPartTail(x+4, 0, &end, &partNum, &numParts)) break;
  158.         } else {
  159.             if (IsPartTail(x, 0, &end, &partNum, &numParts)) {
  160.                 potentialPart = true;
  161.                 break;
  162.             }
  163.         }
  164.         x++;
  165.     }
  166.     p->partNum = partNum;
  167.     p->numParts = numParts;
  168.     p->potentialPart = potentialPart;
  169.     y = p->canon;
  170.     while (*y >= 0 && !isalnum(*y)) y++;
  171.     if (x == y) {
  172.         BlockMoveData(end, x, strlen(end)+1);
  173.     } else {
  174.         *x = 0;
  175.     }
  176.     x = p->canon;
  177.     if (*x == 'v') {
  178.         x++;
  179.         if (isdigit(*x)) {
  180.             x++;
  181.             while (isdigit(*x)) x++;
  182.             if (*x == 'i') {
  183.                 x++;
  184.                 if (isdigit(*x)) {
  185.                     x++;
  186.                     while (isdigit(*x)) x++;
  187.                     if (*x == ':') {
  188.                         x++;
  189.                         while (isLWSP(*x)) x++;
  190.                         BlockMoveData(x, p->canon, strlen(x)+1);
  191.                     }
  192.                 }
  193.             }
  194.         }
  195.     }
  196.     x = p->canon;
  197.     while (*x != 0) x++;
  198.     sprintf(x, "%ld", numParts);
  199. }
  200.  
  201.  
  202.  
  203. /*----------------------------------------------------------------------------
  204.     InitSortInfo 
  205.     
  206.     Initialize sorting info data structures.
  207.     
  208.     Entry:    subjectArray = handle to subject array.
  209.             numSubjects = number of subjects.
  210.             strings = handle to subject strings.
  211.             
  212.     Exit:    function result = error code.
  213.             *sortInfo = handle to locked sort info array.
  214.             *canonicalStrings = handle to locked canonical subject strings.
  215.             *sortInfoPtrs = handle to locked array of pointers into the 
  216.                 sort info array.
  217.             subject array locked.
  218. ----------------------------------------------------------------------------*/
  219.  
  220. static OSErr InitSortInfo (TSubject **subjectArray, long numSubjects, Handle strings,
  221.     TSortInfoHandle *sortInfo, Handle *canonicalStrings, TSortInfoPtr ***sortInfoPtrs)
  222. {
  223.     TSortInfoHandle sInfo = nil;
  224.     Handle cStrings = nil;
  225.     TSortInfoPtr **sInfoPtrs = nil;
  226.     OSErr err = noErr;
  227.     TSortInfoPtr p;
  228.     TSubject *q;
  229.     TSortInfoPtr *r;
  230.     long i;
  231.     char *canon, *x;
  232.     short len, reLen;
  233.     
  234.     /* Allocate memory. */
  235.     
  236.     err = MyNewHandle(numSubjects * sizeof(TSortInfo), &sInfo);
  237.     if (err != noErr) goto exit;
  238.     err = MyNewHandle(MyGetHandleSize(strings), &cStrings);
  239.     if (err != noErr) goto exit;
  240.     err = MyNewHandle(numSubjects * sizeof(TSortInfoPtr), &sInfoPtrs);
  241.     if (err != noErr) goto exit;
  242.     
  243.     /* Lock everything. */
  244.     
  245.     MyHLock(sInfo);
  246.     MyHLock(cStrings);
  247.     MyHLock(sInfoPtrs);
  248.     MyHLock(subjectArray);
  249.     
  250.     for (i = 0, p = *sInfo, q = *subjectArray, r = *sInfoPtrs, canon = *cStrings;
  251.         i < numSubjects;
  252.         i++, p++, q++, r++)
  253.     {
  254.         p->canon = canon;
  255.         p->subject = q;
  256.         p->index = i;
  257.         p->number = q->number;
  258.         p->partNum = p->numParts = kMaxLong;
  259.         p->fromCache = q->read;
  260.         p->potentialPart = false;
  261.         *r = p;
  262.         x = *strings + q->subjectOffset;
  263.         len = strlen(x);
  264.         reLen = ParseRe(x, len);
  265.         x += reLen;
  266.         while (*x != 0) {
  267.             if (isalnum(*x) || *x < 0) {
  268.                 *canon++ = tolower(*x++);
  269.             } else if (*x == '[' || *x == ']' || *x == '(' || *x == ')' ||
  270.                 *x == '/' || *x == '|' || *x == ':') {
  271.                 *canon++ = *x++;
  272.             } else {
  273.                 x++;
  274.             }
  275.         }
  276.         *canon++ = 0;
  277.         CheckForPartIndicator(p);
  278.         if (reLen > 0) {
  279.             p->partNum = p->numParts = kMaxLong;
  280.             p->potentialPart = false;
  281.         }
  282.     }
  283.     
  284.     *sortInfo = sInfo;
  285.     *canonicalStrings = cStrings;
  286.     *sortInfoPtrs = sInfoPtrs;
  287.     return noErr;
  288.     
  289. exit:
  290.  
  291.     MyDisposeHandle(sInfo);
  292.     MyDisposeHandle(cStrings);
  293.     MyDisposeHandle(sInfoPtrs);
  294.     return err;
  295. }
  296.  
  297.  
  298.  
  299. /*----------------------------------------------------------------------------
  300.     SortSubjectArrayCompare1 
  301.     
  302.     This is the comparison function used to sort the array of pointers to 
  303.     TSortInfo records into increasing order by canonical subject.
  304.     
  305.     Entry:    p = pointer to pointer to TSortInfo record.
  306.             q = pointer to pointer to TSortInfo record.
  307.             
  308.     Exit:    function result = error code.
  309.             *result
  310.                 < 0 if first subject < second subject.
  311.                 = 0 if first subject == second subject.
  312.                 > 0 if first subject > second subject.
  313.                 
  314.     The primary sort key is the canonical subject.
  315.     
  316.     The secondary sort key is the part number.
  317.     
  318.     The tertiary sort key is the article number.
  319. ----------------------------------------------------------------------------*/
  320.  
  321. static OSErr SortSubjectArrayCompare1 (TSortInfo **p, TSortInfo **q, short *result)
  322. {
  323.     OSErr err;
  324.     static short counter = 0;
  325.     short r;
  326.  
  327.     if ((++counter & 0x1f) == 0) {
  328.         err = GiveTime(false);
  329.         if (err != noErr) return err;
  330.         counter = 0;
  331.     }
  332.     
  333.     r = strcmp((**p).canon, (**q).canon);
  334.     if (r != 0 ) {
  335.         *result = r;
  336.     } else {
  337.         if ((**p).partNum < (**q).partNum) {
  338.             *result = -1;
  339.         } else if ((**p).partNum > (**q).partNum) {
  340.             *result = +1;
  341.         } else {
  342.             *result = (**p).number < (**q).number ? -1 : +1;
  343.         }
  344.     }
  345.     return noErr;
  346. }
  347.  
  348.  
  349.  
  350. /*----------------------------------------------------------------------------
  351.     SortSubjectArrayCompare2 
  352.     
  353.     This is the comparison function used to sort the array of pointers to 
  354.     TSortInfo records into final thread order.
  355.     
  356.     Entry:    p = pointer to pointer to TSortInfo record.
  357.             q = pointer to pointer to TSortInfo record.
  358.             
  359.     Exit:    function result = error code.
  360.             *result
  361.                 < 0 if first subject < second subject.
  362.                 = 0 if first subject == second subject.
  363.                 > 0 if first subject > second subject.
  364.                 
  365.     The primary sort key is the thread head article number.
  366.     
  367.     The secondary sort key is the thread ordinal.
  368. ----------------------------------------------------------------------------*/
  369.  
  370. static OSErr SortSubjectArrayCompare2 (TSortInfo **p, TSortInfo **q, short *result)
  371. {
  372.     OSErr err;
  373.     static short counter = 0;
  374.  
  375.     if ((++counter & 0x1f) == 0) {
  376.         err = GiveTime(false);
  377.         if (err != noErr) return err;
  378.         counter = 0;
  379.     }
  380.     
  381.     if ((**p).threadHeadNumber == (**q).threadHeadNumber) {
  382.         *result = (**p).threadOrdinal - (**q).threadOrdinal;
  383.     } else {
  384.         *result = (**p).threadHeadNumber < (**q).threadHeadNumber ? -1 : +1;
  385.     }
  386.     return noErr;    
  387. }
  388.  
  389.  
  390.  
  391. /*----------------------------------------------------------------------------
  392.     ProcessThread 
  393.     
  394.     Process a thread.
  395.     
  396.     Entry:    threadHeadPtr = pointer to first element of TSortInfo array
  397.                 for thread.
  398.             threadEndPtr = pointer to element following last element of
  399.                 TSortInfo array for thread.
  400.             
  401.     Exit:    function result = error code.
  402.             
  403.     On entry, the "fromCache" field of each TSortInfo record is true if the 
  404.     article came from the parts cache, false if the article was read
  405.     from the net.
  406.     
  407.     On exit, the following fields are set in each TSubject record:
  408.  
  409.         threadHeadIndex = index in subject array of thread head.
  410.         threadOrdinal = article ordinal within thread (1,2,3,...).    
  411.         threadLength = length of thread.
  412.         nextInThread = index in subject array of next article in thread,
  413.             or -1 if last article in thread. 
  414.         partNum = part number, or kMaxLong if not a part.
  415.         numParts = number of parts, or kMaxLong if not a part.
  416.         read = false
  417.         incomplete = true if article is part of an incomplete multiple
  418.             part thread.        
  419.         inList = true if article should be included in list displayed
  420.             to user (thread contains at least one unread article).
  421.             
  422.     On exit, the following fields are set in each TSortInfo record:
  423.         
  424.         threadHeadNumber = article number of first article in thread.
  425.         threadOrdinal = article ordinal within thread (1,2,3,...).
  426.         
  427.     On exit, the following fields in the TSortInfo records may be
  428.     adjusted:
  429.     
  430.         partNum = part number, or kMaxLong if not a part.
  431.         numParts = number of parts, 0r kMaxLong if not a part.
  432. ----------------------------------------------------------------------------*/
  433.  
  434. static OSErr ProcessThread (TSortInfo **threadHeadPtr, TSortInfo **threadEndPtr)
  435. {
  436.     TSortInfo **s, *x;
  437.     TSubject *q, *prevInList;
  438.     long threadLength, threadHeadNumber, threadHeadIndex, threadOrdinal;
  439.     long numParts, lastPart, numArtsWhichAreParts;
  440.     Boolean inList, incomplete, haveNonPotentialPart, cacheParts;
  441.     Boolean haveNewPart, complete, haveOldPart;
  442.     CStr255 subject, author;
  443.     OSErr err = noErr;
  444.     
  445.     numParts = 0;
  446.     haveNewPart = false;
  447.     haveOldPart = false;
  448.     numArtsWhichAreParts = 0;
  449.     haveNonPotentialPart = false;
  450.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  451.         x = *s;
  452.         if (x->fromCache && x->numParts < kMaxLong) haveOldPart = true;
  453.         if (!x->fromCache && x->numParts < kMaxLong) haveNewPart = true;
  454.         if (x->numParts < kMaxLong) {
  455.             if (x->numParts > numParts) numParts = x->numParts;
  456.             numArtsWhichAreParts++;
  457.             if (!x->potentialPart) haveNonPotentialPart = true;
  458.         }
  459.     }
  460.     
  461.     if (numArtsWhichAreParts == 0) {
  462.         incomplete = false;
  463.         cacheParts = false;
  464.         complete = false;
  465.     } else if (numArtsWhichAreParts == 1 && !haveNonPotentialPart) {
  466.         incomplete = false;
  467.         cacheParts = true;
  468.         complete = false;
  469.     } else {
  470.         cacheParts = true;
  471.         lastPart = 0;
  472.         for (s = threadHeadPtr; s < threadEndPtr; s++) {
  473.             x = *s;
  474.             if (x->partNum == lastPart+1) lastPart = x->partNum;
  475.         }
  476.         cacheParts = incomplete = lastPart < numParts;
  477.         complete = !incomplete && haveOldPart;
  478.     }
  479.     
  480.     threadHeadNumber = (**threadHeadPtr).number;
  481.     threadOrdinal = 1;
  482.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  483.         x = *s;
  484.         x->threadHeadNumber = threadHeadNumber;
  485.         x->threadOrdinal = threadOrdinal;
  486.         q = x->subject;
  487.         inList = !x->fromCache || 
  488.             (!incomplete && haveNewPart && x->numParts < kMaxLong);
  489.         if (inList) {
  490.             if (threadOrdinal == 1) threadHeadIndex = x->index;
  491.             if (cacheParts && !x->fromCache && x->partNum < kMaxLong && 
  492.                 gParentIsUserGroupList) 
  493.             {
  494.                 /* This is a new part in an incomplete thread - 
  495.                    add it to the cache */
  496.                 strcpy(subject, *gStrings + q->subjectOffset);
  497.                 strcpy(author, *gStrings + q->authorOffset);
  498.                 err = AddCachedArticle(gGroupName, q->number, subject, author);
  499.                 if (err != noErr) return err;
  500.             } else if (!incomplete && x->fromCache && x->partNum < kMaxLong &&
  501.                 gParentIsUserGroupList) 
  502.             {
  503.                 /* This is part of a thread which is now
  504.                    complete - remove it from the cache */
  505.                 err = DeleteCachedArticle(gGroupName, q->number);
  506.                 if (err != noErr) return err;
  507.             }
  508.             q->threadHeadIndex = threadHeadIndex;
  509.             q->threadOrdinal = threadOrdinal;
  510.             if (numArtsWhichAreParts == 1 && x->potentialPart)
  511.                 x->partNum = x->numParts = kMaxLong;
  512.             q->partNum = x->partNum;
  513.             q->numParts = x->numParts;
  514.             q->read = false;
  515.             q->incomplete = incomplete;
  516.             q->complete = complete;
  517.             q->inList = true;
  518.             threadOrdinal++;
  519.         } else {
  520.             q->inList = false;
  521.         }
  522.     }
  523.     
  524.     threadLength = threadOrdinal - 1;
  525.     if (threadLength == 0) return noErr;
  526.     
  527.     prevInList = nil;
  528.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  529.         x = *s;
  530.         q = x->subject;
  531.         if (q->inList) {
  532.             q->threadLength = threadLength;
  533.             if (prevInList != nil) prevInList->nextInThread = x->index;
  534.             prevInList = q;
  535.         }
  536.     }
  537.     prevInList->nextInThread = -1;
  538.     
  539.     return noErr;
  540. }
  541.  
  542.  
  543.  
  544. /*----------------------------------------------------------------------------
  545.     SortArray 
  546.     
  547.     Sort the array of TSortInfo pointers into thread order. 
  548.     
  549.     Entry:    sortInfoPtrs = pointer to array of pointers to TSortInfo records.
  550.             numSubjects = number of subjects.
  551.             
  552.     Exit:    function result = error code. 
  553. ----------------------------------------------------------------------------*/
  554.  
  555. static OSErr SortArray (TSortInfoPtr *sortInfoPtrs, long numSubjects)
  556. {
  557.     TSortInfo **r, **threadHeadPtr;
  558.     char *threadHeadSubject;
  559.     Boolean newThread;
  560.     long i;
  561.     OSErr err = noErr;
  562.     
  563.     /*    Sort the pointer array into increasing order by canonical subject. 
  564.         This brings threads together, although the threads are not in the 
  565.         right order yet. The article numbers are used as a secondary sort 
  566.         key to kep the articles within a thread in the correct order. */
  567.         
  568.     err = FastQSort(sortInfoPtrs, numSubjects, sizeof(TSortInfoPtr),
  569.         (SortCmpFunction)SortSubjectArrayCompare1);
  570.     if (err != noErr) return err;
  571.     
  572.     /*    Process the threads. */
  573.         
  574.     for (i = 0, r = sortInfoPtrs; i < numSubjects; i++, r++) {
  575.         newThread = i == 0 || strcmp((**r).canon, threadHeadSubject) != 0;
  576.         if (newThread) {
  577.             if (i != 0) {
  578.                 err = ProcessThread(threadHeadPtr, r);
  579.                 if (err != noErr) return err;
  580.             }
  581.             threadHeadPtr = r;
  582.             threadHeadSubject = (**r).canon;
  583.         }
  584.     }
  585.     err = ProcessThread(threadHeadPtr, r);
  586.     if (err != noErr) return err;
  587.     
  588.     /*    Sort the pointer array into final thread order. The primary
  589.         sort key is threadHeadNumber. The secondary sort key is the
  590.         thread ordinal. This final sort sorts the threads into proper 
  591.         chronological order, keeping the articles within the threads 
  592.         together. */
  593.         
  594.     return FastQSort(sortInfoPtrs, numSubjects, sizeof(TSortInfoPtr),
  595.         (SortCmpFunction)SortSubjectArrayCompare2);
  596. }
  597.  
  598.  
  599.  
  600. /*----------------------------------------------------------------------------
  601.     SplitPartThreads
  602.     
  603.     Split part threads into two threads, the first one containing just the
  604.     parts, and the second one containing just the followups.
  605.     
  606.     Entry:    subjectArray = pointer to subject array
  607.             numSubjects = number of subjects.
  608. ----------------------------------------------------------------------------*/
  609.  
  610. static void SplitPartThreads (TSubject *subjectArray, long numSubjects)
  611. {
  612.     long i, j;
  613.     TSubject *s, *t;
  614.     long partsThreadLength, followupsThreadLength;
  615.     long followupsThreadHeadIndex;
  616.     long nextInThread;
  617.         
  618.     for (i = 0, s = subjectArray; i < numSubjects; i++, s++) {
  619.         if (!s->inList) continue;
  620.         if (s->threadOrdinal != 1) continue;
  621.         if (s->threadLength == 1) continue;
  622.         if (s->partNum == kMaxLong) continue;
  623.         t = s;
  624.         partsThreadLength = 0;
  625.         while (t->partNum < kMaxLong && t->nextInThread != -1) {
  626.             partsThreadLength++;
  627.             t = subjectArray + t->nextInThread;
  628.         }
  629.         if (t->partNum < kMaxLong) continue;
  630.         t = s;
  631.         for (j = 1; j <= partsThreadLength; j++) {
  632.             t->threadLength = partsThreadLength;
  633.             nextInThread = t->nextInThread;
  634.             if (j == partsThreadLength) {
  635.                 followupsThreadHeadIndex = nextInThread;
  636.                 t->nextInThread = -1;
  637.             }
  638.             t = subjectArray + nextInThread;
  639.         }
  640.         followupsThreadLength = t->threadLength - partsThreadLength;
  641.         for (j = 1; j <= followupsThreadLength; j++) {
  642.             t->threadHeadIndex = followupsThreadHeadIndex;
  643.             t->threadOrdinal = j;
  644.             t->threadLength = followupsThreadLength;
  645.             t->incomplete = t->complete = false;
  646.             t = subjectArray + t->nextInThread;
  647.         }
  648.     }
  649. }
  650.  
  651.  
  652.  
  653. /*----------------------------------------------------------------------------
  654.     SortSubjectArrayCompare3
  655.     
  656.     This is the comparison function used to sort an array of pointers to 
  657.     TSubject records into increasing order by article number. It is used
  658.     by the BuildSortArrayByNumber function below.
  659.     
  660.     Entry:    p = pointer to pointer to TSubject record.
  661.             q = pointer to pointer to TSubject record.
  662.             
  663.     Exit:    function result = error code.
  664.             *result
  665.                 < 0 if first article number < second article number.
  666.                 > 0 if first article number > second article number.
  667. ----------------------------------------------------------------------------*/
  668.  
  669. static OSErr SortSubjectArrayCompare3 (TSubject **p, TSubject **q, short *result)
  670. {
  671.     OSErr err;
  672.     static short counter = 0;
  673.  
  674.     if ((++counter & 0x1f) == 0) {
  675.         err = GiveTime(false);
  676.         if (err != noErr) return err;
  677.         counter = 0;
  678.     }
  679.     
  680.     *result = (**p).number < (**q).number ? -1 : +1;
  681.     return noErr;
  682. }
  683.  
  684.  
  685.  
  686. /*----------------------------------------------------------------------------
  687.     BuildSortByNumberArray 
  688.     
  689.     Build the SortByNumber array for the subject window.
  690.     
  691.     Entry:    wind = pointer to window record.
  692.             
  693.     Exit:    function result = error code.
  694.             (**info).sortByNumber = handle to array of offsets to elements of
  695.                 subject array which are in the list, sorted by article
  696.                 number. 
  697. ----------------------------------------------------------------------------*/
  698.  
  699. static OSErr BuildSortByNumberArray (WindowPtr wind)
  700. {
  701.     TWindow **info;
  702.     TSubject **subjectArray;
  703.     long numSubjects, numSubjectsInList;
  704.     long **sortByNumber = nil;
  705.     OSErr err = noErr;
  706.     long i;
  707.     TSubject *p;
  708.     long *q;
  709.     char state;
  710.     
  711.     info = (TWindow**)GetWRefCon(wind);
  712.     subjectArray = (**info).subjectArray;
  713.     state = MyHGetState(subjectArray);
  714.     numSubjects = (**info).numSubjects;
  715.     numSubjectsInList = (**info).numSubjectsInList;
  716.     
  717.     /* Allocate the array. */
  718.     
  719.     err = MyNewHandle(numSubjectsInList*sizeof(long), &sortByNumber);
  720.     if (err != noErr) goto exit;
  721.     
  722.     /* Initialize the array. During the sort, the array elements are pointers
  723.        into the locked subject array, rather than offsets. */
  724.     
  725.     MyHLock(sortByNumber);
  726.     MyHLock(subjectArray);
  727.  
  728.     for (i = 0, p = *subjectArray, q = *sortByNumber;
  729.         i < numSubjects;
  730.         i++, p++)
  731.     {
  732.         if (p->inList) *q++ = (long)p;
  733.     }
  734.     
  735.     /* Sort the array into increasing order by article number. */
  736.  
  737.     err = FastQSort(*sortByNumber, numSubjectsInList, sizeof(long),
  738.         (SortCmpFunction)SortSubjectArrayCompare3);
  739.     if (err != noErr) goto exit;
  740.     
  741.     /* Convert the array from pointers to offsets. */
  742.     
  743.     for (i = 0, q = *sortByNumber; i < numSubjectsInList; i++, q++)
  744.         *q = (char*)*q - (char*)*subjectArray;
  745.     
  746.     MyHUnlock(sortByNumber);
  747.     MyHSetState(subjectArray, state);
  748.         
  749.     (**info).sortByNumber = sortByNumber;
  750.     return noErr;
  751.     
  752. exit:
  753.  
  754.     MyDisposeHandle(sortByNumber);
  755.     MyHSetState(subjectArray, state);
  756.     return err;
  757. }
  758.  
  759.  
  760.  
  761. /*----------------------------------------------------------------------------
  762.     BuildThreads 
  763.     
  764.     Build subject window threads.
  765.     
  766.     Entry:    wind = pointer to subject window.
  767.     
  768.     Exit:    function result = error code.
  769. ----------------------------------------------------------------------------*/
  770.  
  771. OSErr BuildThreads (WindowPtr wind)
  772. {
  773.     TWindow **info, **parentInfo;
  774.     WindowPtr parent;
  775.     TSubject **subjectArray;
  776.     long numSubjects, numSubjectsInList;
  777.     BigListRef subjectList;
  778.     TSortInfoHandle sortInfo = nil;
  779.     TSortInfoPtr **sortInfoPtrs = nil;
  780.     Handle canonicalStrings = nil;
  781.     TSubject *q;
  782.     TSortInfoPtr *r;
  783.     long numItems, item;
  784.     long i;
  785.     OSErr err = noErr;
  786.     char state;
  787.     
  788.     /* Initialize. */
  789.     
  790.     info = (TWindow**)GetWRefCon(wind);
  791.     subjectArray = (**info).subjectArray;
  792.     state = MyHGetState(subjectArray);
  793.     gStrings = (**info).strings;
  794.     subjectList = (**info).subjectList;
  795.     strcpy(gGroupName, *gGroupNames + (**info).groupNameOffset);
  796.     parent = (**info).parentWindow;
  797.     if (parent == nil) {
  798.         gParentIsUserGroupList = false;
  799.     } else {
  800.         parentInfo = (TWindow**)GetWRefCon(parent);
  801.         gParentIsUserGroupList = (**parentInfo).groupKind == kUserGroup;
  802.     }
  803.     
  804.     /* Append cached articles to subject array. */
  805.     
  806.     if (gParentIsUserGroupList) {
  807.         err = AppendCachedArticles(wind);
  808.         if (err != noErr) goto exit;
  809.     }
  810.     numSubjects = (**info).numSubjects;
  811.  
  812.     /* Initialize the sort info data structures. */
  813.  
  814.     err = InitSortInfo(subjectArray, numSubjects, gStrings,
  815.         &sortInfo, &canonicalStrings, &sortInfoPtrs);
  816.     if (err != noErr) goto exit;
  817.  
  818.     /* Sort the sortInfoPtrs array into thread order. */
  819.     
  820.     err = SortArray(*sortInfoPtrs, numSubjects);
  821.     if (err != noErr) goto exit;
  822.     
  823.     /* We can and should get rid of the canonicalStrings memory block at this point. */
  824.     
  825.     MyDisposeHandle(canonicalStrings);
  826.     canonicalStrings = nil;
  827.     
  828.     /* Split part threads. */
  829.     
  830.     SplitPartThreads(*subjectArray, numSubjects);
  831.     
  832.     /* Compute the number of subjects in the list and the number
  833.        of cells in the list. */
  834.     
  835.     numSubjectsInList = numSubjects;
  836.     numItems = numSubjects;
  837.     for (i = 0, q = *subjectArray; i < numSubjects; i++, q++) {
  838.         if (!q->inList) {
  839.             numSubjectsInList--;
  840.             numItems--;
  841.         } else if (q->collapsed && q->threadOrdinal != 1) {
  842.             numItems--;
  843.         }
  844.     }
  845.     (**info).numSubjectsInList = numSubjectsInList;
  846.  
  847.     /* Create the subject list. */
  848.     
  849.     err = BigLAddItems(subjectList, 0, numItems);
  850.     if (err != noErr) goto exit;
  851.     for (i = 0, item = 0, r = *sortInfoPtrs; i < numSubjects; i++, r++) {
  852.         q = (**r).subject;
  853.         if (q->inList) {
  854.             if (!q->collapsed || q->threadOrdinal == 1) {
  855.                 BigLSetData(subjectList, item, (*r)->index);
  856.                 item++;
  857.             }
  858.         }
  859.     }
  860.     
  861.     /* Dispose the sortInfo and sortInfoPtrs memory blocks. */
  862.         
  863.     MyHSetState(subjectArray, state);
  864.     MyDisposeHandle(sortInfo);
  865.     MyDisposeHandle(sortInfoPtrs);
  866.     
  867.     /* Build the sort by number array. */
  868.     
  869.     return BuildSortByNumberArray(wind);
  870.     
  871. exit:
  872.  
  873.     MyHSetState(subjectArray, state);
  874.     MyDisposeHandle(sortInfo);
  875.     MyDisposeHandle(canonicalStrings);
  876.     MyDisposeHandle(sortInfoPtrs);
  877.     return err;
  878. }
  879.